﻿using System;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

using JavaSubstitutes;

namespace Sprites
{
	public class LevelGenerator
	{
		public const int TYPE_OVERGROUND = 0;
		public const int TYPE_UNDERGROUND = 1;
		public const int TYPE_CASTLE = 2;

		public static int lastSeed;

		private int width = 0;
		private int height = 0;
		//Level level = new Level(width, height);
		Level level = new Level(0,0); //?

		private const int ODDS_STRAIGHT = 0;
		private const int ODDS_HILL_STRAIGHT = 1;
		private const int ODDS_TUBES = 2;
		private const int ODDS_JUMP = 3;
		private const int ODDS_CANNONS = 4;
		private int[] odds = new int[5];
		private int totalOdds;
		private int difficulty;
		private int type;

		private LevelGenerator(int width, int height)
		{
			this.width = width;
			this.height = height;
		}

		public static Level createLevel(int width, int height, int seed, int difficulty, int type)
		{
			LevelGenerator levelGenerator = new LevelGenerator(width, height);
			return levelGenerator.createLevel(seed, difficulty, type);
		}

		private Level createLevel(int seed, int difficulty, int type)
		{
			this.type = type;
			this.difficulty = difficulty;
			odds[ODDS_STRAIGHT] = 20;
			odds[ODDS_HILL_STRAIGHT] = 10;
			odds[ODDS_TUBES] = 2 + 1 * difficulty;
			odds[ODDS_JUMP] = 2 * difficulty;
			odds[ODDS_CANNONS] = -10 + 5 * difficulty;

			if (type != LevelGenerator.TYPE_OVERGROUND)
			{
				odds[ODDS_HILL_STRAIGHT] = 0;
			}

			for (int i = 0; i < odds.Length; i++)
			{
				if (odds[i] < 0) odds[i] = 0;
				totalOdds += odds[i];
				odds[i] = totalOdds - odds[i];
			}

			lastSeed = seed;
			level = new Level(width, height);

			int length = 0;
			length += buildStraight(0, level.width, true);
			while (length < level.width - 64)
			{
				length += buildZone(length, level.width - length);
			}

			int Floor = height - 1 - RandomExtensions.nextInt(4);

			level.xExit = length + 8;
			level.yExit = Floor;

			for (int x = length; x < level.width; x++)
			{
				for (int y = 0; y < height; y++)
				{
					if (y >= Floor)
					{
						level.setBlock(x, y, (byte)(1 + 9 * 16));
					}
				}
			}

			if (type == LevelGenerator.TYPE_CASTLE || type == LevelGenerator.TYPE_UNDERGROUND)
			{
				int ceiling = 0;
				int run = 0;
				for (int x = 0; x < level.width; x++)
				{
					if (run-- <= 0 && x > 4)
					{
						ceiling = RandomExtensions.nextInt(4);
						run = RandomExtensions.nextInt(4) + 4;
					}
					for (int y = 0; y < level.height; y++)
					{
						if ((x > 4 && y <= ceiling) || x < 1)
						{
							level.setBlock(x, y, (byte)(1 + 9 * 16));
						}
					}
				}
			}

			fixWalls();

			return level;
		}

		private int buildZone(int x, int maxLength)
		{
			int t = RandomExtensions.nextInt(totalOdds);
			int type = 0;
			for (int i = 0; i < odds.Length; i++)
			{
				if (odds[i] <= t)
				{
					type = i;
				}
			}

			switch (type)
			{
				case ODDS_STRAIGHT:
					return buildStraight(x, maxLength, false);
				case ODDS_HILL_STRAIGHT:
					return buildHillStraight(x, maxLength);
				case ODDS_TUBES:
					return buildTubes(x, maxLength);
				case ODDS_JUMP:
					return buildJump(x, maxLength);
				case ODDS_CANNONS:
					return buildCannons(x, maxLength);
			}
			return 0;
		}

		private int buildJump(int xo, int maxLength)
		{
			int js = RandomExtensions.nextInt(4) + 2;
			int jl = RandomExtensions.nextInt(2) + 2;
			int length = js * 2 + jl;

			bool hasStairs = RandomExtensions.nextInt(3) == 0;

			int Floor = height - 1 - RandomExtensions.nextInt(4);
			for (int x = xo; x < xo + length; x++)
			{
				if (x < xo + js || x > xo + length - js - 1)
				{
					for (int y = 0; y < height; y++)
					{
						if (y >= Floor)
						{
							level.setBlock(x, y, (byte)(1 + 9 * 16));
						}
						else if (hasStairs)
						{
							if (x < xo + js)
							{
								if (y >= Floor - (x - xo) + 1)
								{
									level.setBlock(x, y, (byte)(9 + 0 * 16));
								}
							}
							else
							{
								if (y >= Floor - ((xo + length) - x) + 2)
								{
									level.setBlock(x, y, (byte)(9 + 0 * 16));
								}
							}
						}
					}
				}
			}

			return length;
		}

		private int buildCannons(int xo, int maxLength)
		{
			int length = RandomExtensions.nextInt(10) + 2;
			if (length > maxLength) length = maxLength;

			int Floor = height - 1 - RandomExtensions.nextInt(4);
			int xCannon = xo + 1 + RandomExtensions.nextInt(4);
			for (int x = xo; x < xo + length; x++)
			{
				if (x > xCannon)
				{
					xCannon += 2 + RandomExtensions.nextInt(4);
				}
				if (xCannon == xo + length - 1) xCannon += 10;
				int cannonHeight = Floor - RandomExtensions.nextInt(4) - 1;

				for (int y = 0; y < height; y++)
				{
					if (y >= Floor)
					{
						level.setBlock(x, y, (byte)(1 + 9 * 16));
					}
					else
					{
						if (x == xCannon && y >= cannonHeight)
						{
							if (y == cannonHeight)
							{
								level.setBlock(x, y, (byte)(14 + 0 * 16));
							}
							else if (y == cannonHeight + 1)
							{
								level.setBlock(x, y, (byte)(14 + 1 * 16));
							}
							else
							{
								level.setBlock(x, y, (byte)(14 + 2 * 16));
							}
						}
					}
				}
			}

			return length;
		}

		private int buildHillStraight(int xo, int maxLength)
		{
			int length = RandomExtensions.nextInt(10) + 10;
			if (length > maxLength) length = maxLength;

			int Floor = height - 1 - RandomExtensions.nextInt(4);
			for (int x = xo; x < xo + length; x++)
			{
				for (int y = 0; y < height; y++)
				{
					if (y >= Floor)
					{
						level.setBlock(x, y, (byte)(1 + 9 * 16));
					}
				}
			}

			addEnemyLine(xo + 1, xo + length - 1, Floor - 1);

			int h = Floor;

			bool keepGoing = true;

			bool[] occupied = new bool[length];
			while (keepGoing)
			{
				h = h - 2 - RandomExtensions.nextInt(3);

				if (h <= 0)
				{
					keepGoing = false;
				}
				else
				{
					int l = RandomExtensions.nextInt(5) + 3;
					int xxo = RandomExtensions.nextInt(length - l - 2) + xo + 1;

					if (occupied[xxo - xo] || occupied[xxo - xo + l] || occupied[xxo - xo - 1] || occupied[xxo - xo + l + 1])
					{
						keepGoing = false;
					}
					else
					{
						occupied[xxo - xo] = true;
						occupied[xxo - xo + l] = true;
						addEnemyLine(xxo, xxo + l, h - 1);
						if (RandomExtensions.nextInt(4) == 0)
						{
							decorate(xxo - 1, xxo + l + 1, h);
							keepGoing = false;
						}
						for (int x = xxo; x < xxo + l; x++)
						{
							for (int y = h; y < Floor; y++)
							{
								int xx = 5;
								if (x == xxo) xx = 4;
								if (x == xxo + l - 1) xx = 6;
								int yy = 9;
								if (y == h) yy = 8;

								if (level.getBlock(x, y) == 0)
								{
									level.setBlock(x, y, (byte)(xx + yy * 16));
								}
								else
								{
									if (level.getBlock(x, y) == (byte)(4 + 8 * 16)) level.setBlock(x, y, (byte)(4 + 11 * 16));
									if (level.getBlock(x, y) == (byte)(6 + 8 * 16)) level.setBlock(x, y, (byte)(6 + 11 * 16));
								}
							}
						}
					}
				}
			}

			return length;
		}

		private void addEnemyLine(int x0, int x1, int y)
		{
			for (int x = x0; x < x1; x++)
			{
				if (RandomExtensions.nextInt(35) < difficulty + 1)
				{
					int type = RandomExtensions.nextInt(4);
					if (difficulty < 1)
					{
						type = Enemy.ENEMY_GOOMBA;
					}
					else if (difficulty < 3)
					{
						type = RandomExtensions.nextInt(3);
					}
					level.setSpriteTemplate(x, y, new SpriteTemplate(type, RandomExtensions.nextInt(35) < difficulty));
				}
			}
		}

		private int buildTubes(int xo, int maxLength)
		{
			int length = RandomExtensions.nextInt(10) + 5;
			if (length > maxLength) length = maxLength;

			int Floor = height - 1 - RandomExtensions.nextInt(4);
			int xTube = xo + 1 + RandomExtensions.nextInt(4);
			int tubeHeight = Floor - RandomExtensions.nextInt(2) - 2;
			for (int x = xo; x < xo + length; x++)
			{
				if (x > xTube + 1)
				{
					xTube += 3 + RandomExtensions.nextInt(4);
					tubeHeight = Floor - RandomExtensions.nextInt(2) - 2;
				}
				if (xTube >= xo + length - 2) xTube += 10;

				if (x == xTube && RandomExtensions.nextInt(11) < difficulty + 1)
				{
					level.setSpriteTemplate(x, tubeHeight, new SpriteTemplate(Enemy.ENEMY_FLOWER, false));
				}

				for (int y = 0; y < height; y++)
				{
					if (y >= Floor)
					{
						level.setBlock(x, y, (byte)(1 + 9 * 16));
					}
					else
					{
						if ((x == xTube || x == xTube + 1) && y >= tubeHeight)
						{
							int xPic = 10 + x - xTube;
							if (y == tubeHeight)
							{
								level.setBlock(x, y, (byte)(xPic + 0 * 16));
							}
							else
							{
								level.setBlock(x, y, (byte)(xPic + 1 * 16));
							}
						}
					}
				}
			}

			return length;
		}

		private int buildStraight(int xo, int maxLength, bool safe)
		{
			int length = RandomExtensions.nextInt(10) + 2;
			if (safe) length = 10 + RandomExtensions.nextInt(5);
			if (length > maxLength) length = maxLength;

			int Floor = height - 1 - RandomExtensions.nextInt(4);
			for (int x = xo; x < xo + length; x++)
			{
				for (int y = 0; y < height; y++)
				{
					if (y >= Floor)
					{
						level.setBlock(x, y, (byte)(1 + 9 * 16));
					}
				}
			}

			if (!safe)
			{
				if (length > 5)
				{
					decorate(xo, xo + length, Floor);
				}
			}

			return length;
		}

		private void decorate(int x0, int x1, int Floor)
		{
			if (Floor < 1) return;

			//        bool coins = RandomExtensions.nextInt(3) == 0;
			bool rocks = true;

			addEnemyLine(x0 + 1, x1 - 1, Floor - 1);

			int s = RandomExtensions.nextInt(4);
			int e = RandomExtensions.nextInt(4);

			if (Floor - 2 > 0)
			{
				if ((x1 - 1 - e) - (x0 + 1 + s) > 1)
				{
					for (int x = x0 + 1 + s; x < x1 - 1 - e; x++)
					{
						level.setBlock(x, Floor - 2, (byte)(2 + 2 * 16));
					}
				}
			}

			s = RandomExtensions.nextInt(4);
			e = RandomExtensions.nextInt(4);

			if (Floor - 4 > 0)
			{
				if ((x1 - 1 - e) - (x0 + 1 + s) > 2)
				{
					for (int x = x0 + 1 + s; x < x1 - 1 - e; x++)
					{
						if (rocks)
						{
							if (x != x0 + 1 && x != x1 - 2 && RandomExtensions.nextInt(3) == 0)
							{
								if (RandomExtensions.nextInt(4) == 0)
								{
									level.setBlock(x, Floor - 4, (byte)(4 + 2 + 1 * 16));
								}
								else
								{
									level.setBlock(x, Floor - 4, (byte)(4 + 1 + 1 * 16));
								}
							}
							else if (RandomExtensions.nextInt(4) == 0)
							{
								if (RandomExtensions.nextInt(4) == 0)
								{
									level.setBlock(x, Floor - 4, (byte)(2 + 1 * 16));
								}
								else
								{
									level.setBlock(x, Floor - 4, (byte)(1 + 1 * 16));
								}
							}
							else
							{
								level.setBlock(x, Floor - 4, (byte)(0 + 1 * 16));
							}
						}
					}
				}
			}

			int length = x1 - x0 - 2;

			/*        if (length > 5 && rocks)
			 {
			 decorate(x0, x1, Floor - 4);
			 }*/
		}

		private void fixWalls()
		{
			bool[,] blockMap = new bool[width + 1, height + 1];
			for (int x = 0; x < width + 1; x++)
			{
				for (int y = 0; y < height + 1; y++)
				{
					int blocks = 0;
					for (int xx = x - 1; xx < x + 1; xx++)
					{
						for (int yy = y - 1; yy < y + 1; yy++)
						{
							if (level.getBlockCapped(xx, yy) == (byte)(1 + 9 * 16)) blocks++;
						}
					}
					blockMap[x, y] = blocks == 4;
				}
			}
			blockify(level, blockMap, width + 1, height + 1);
		}

		private void blockify(Level level, bool[,] blocks, int width, int height)
		{
			int to = 0;
			if (type == LevelGenerator.TYPE_CASTLE)
			{
				to = 4 * 2;
			}
			else if (type == LevelGenerator.TYPE_UNDERGROUND)
			{
				to = 4 * 3;
			}

			bool[,] b = new bool[2, 2];
			for (int x = 0; x < width; x++)
			{
				for (int y = 0; y < height; y++)
				{
					for (int xx = x; xx <= x + 1; xx++)
					{
						for (int yy = y; yy <= y + 1; yy++)
						{
							int _xx = xx;
							int _yy = yy;
							if (_xx < 0) _xx = 0;
							if (_yy < 0) _yy = 0;
							if (_xx > width - 1) _xx = width - 1;
							if (_yy > height - 1) _yy = height - 1;
							b[xx - x, yy - y] = blocks[_xx, _yy];
						}
					}

					if (b[0, 0] == b[1, 0] && b[0, 1] == b[1, 1])
					{
						if (b[0, 0] == b[0, 1])
						{
							if (b[0, 0])
							{
								level.setBlock(x, y, (byte)(1 + 9 * 16 + to));
							}
							else
							{
								// KEEP OLD BLOCK!
							}
						}
						else
						{
							if (b[0, 0])
							{
								level.setBlock(x, y, (byte)(1 + 10 * 16 + to));
							}
							else
							{
								level.setBlock(x, y, (byte)(1 + 8 * 16 + to));
							}
						}
					}
					else if (b[0, 0] == b[0, 1] && b[1, 0] == b[1, 1])
					{
						if (b[0, 0])
						{
							level.setBlock(x, y, (byte)(2 + 9 * 16 + to));
						}
						else
						{
							level.setBlock(x, y, (byte)(0 + 9 * 16 + to));
						}
					}
					else if (b[0, 0] == b[1, 1] && b[0, 1] == b[1, 0])
					{
						level.setBlock(x, y, (byte)(1 + 9 * 16 + to));
					}
					else if (b[0, 0] == b[1, 0])
					{
						if (b[0, 0])
						{
							if (b[0, 1])
							{
								level.setBlock(x, y, (byte)(3 + 10 * 16 + to));
							}
							else
							{
								level.setBlock(x, y, (byte)(3 + 11 * 16 + to));
							}
						}
						else
						{
							if (b[0, 1])
							{
								level.setBlock(x, y, (byte)(2 + 8 * 16 + to));
							}
							else
							{
								level.setBlock(x, y, (byte)(0 + 8 * 16 + to));
							}
						}
					}
					else if (b[0, 1] == b[1, 1])
					{
						if (b[0, 1])
						{
							if (b[0, 0])
							{
								level.setBlock(x, y, (byte)(3 + 9 * 16 + to));
							}
							else
							{
								level.setBlock(x, y, (byte)(3 + 8 * 16 + to));
							}
						}
						else
						{
							if (b[0, 0])
							{
								level.setBlock(x, y, (byte)(2 + 10 * 16 + to));
							}
							else
							{
								level.setBlock(x, y, (byte)(0 + 10 * 16 + to));
							}
						}
					}
					else
					{
						level.setBlock(x, y, (byte)(0 + 1 * 16 + to));
					}
				}
			}
		}
	}
}